{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 问题：元类有什么用\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "面试官想从这道题中考察面试人什么？\n",
    "Python 是一门动态语言，元类是体现其动态特性的一个技术之一。也许你不会有机会去创建一个元类，但你可能会去读到一些源码，那么碰到元类的概率还是挺大的。这也是面试官考你元类的主要目的。\n",
    "\n",
    "答案\n",
    "元类可以动态地创建类，它是制造类的工厂。\n",
    "\n",
    "拓展\n",
    "Python 里一切都是对象，类也是对象。\n",
    "\n",
    "元类究竟是什么样的一个东西？在了解元类之前，我们必须了解类的概念。Python 中的类和其它语言中的类有所不同，其它语言中的类可以创建实例对象，让具有同类的实例对象可以拥有一样的属性和行为，简而言之，类是一种抽象出的模板。但是，在 Python 中，类也是对象，也就是说，还有创造出类的模板，它就是元类。类定义了一个类实例的行为，那么，元类定义了类的行为。\n",
    "\n",
    "实例对象（instance）是类（class）的是实例，类（class）也是对象，是 type 的实例。type 对象本身又是 type 类的实例，type 是 Python 自建的元类。\n",
    "\n",
    "如果你想定义一个元类，那么就去定义一个 type 的继承类。\n",
    "\n",
    ">>> type(\"abc\") # \"abc\"是实例对象\n",
    "<class 'str'>   # \"abc\"的类型str也是对象，一种class对象\n",
    ">>> type(str)   # 所以，str也有它的类型，它的类型就是type\n",
    "<class 'type'>\n",
    ">>> type(type)  # type的类型是其本身，type也是对象\n",
    "<class 'type'>\n",
    "type () 函数能返回一个对象的类型 (类型本身也是对象)，也就是返回对象的__class__属性值。\n",
    "\n",
    "如何创建 class 对象呢？\n",
    "\n",
    "最简单的办法就是使用 class 语句：\n",
    "\n",
    "class MyClass: #python3默认是创建新式类，隐式继承了object\n",
    "    a = 1\n",
    "还有另外一种办法，使用 type(name, bases, namespace) 函数，该函数会返回一个类型对象：\n",
    "\n",
    "MyClass = type('MyClass', (object,), dict(a=1))\n",
    "name 对应于类的__name__属性\n",
    "bases 对应于类的__bases__属性\n",
    "namespace 对应于类的__dict__属性\n",
    "class 对象和 type 对象都是 类型对象 ，类型对象表示各种对象类型，而实例对象不是类型对象。一般我们认为 类型对象可以表示一种抽象的数据类型，它们可以被子类继承，也可以被实例化出该类型的对象。一种最简单的判别方法就是判断该对象是否是 type 的实例，是的话就是类型对象，否则就不是。\n",
    "\n",
    ">>> isinstance(str, type)\n",
    "True\n",
    ">>> isinstance(type, type)\n",
    "True\n",
    ">>> isinstance(MyClass, type)\n",
    "True\n",
    ">>> isinstance('1', type)\n",
    "False\n",
    "type() 函数可以动态创建类，即在运行期动态创建类。除了使用 type () 动态创建类以外，要控制类的创建行为，还可以使用 metaclass。那么，我们必须先定义一个元类，而且它必须继承 type 类。比如我们想让自定义的类所创建的的对象自动包含 add 方法，即使类本身没有定义这个方法。\n",
    "\n",
    "# metaclass是类的模板，所以必须从`type`类型派生：\n",
    "class ListMetaclass(type):\n",
    "    # __new__ 是在__init__之前被调用的特殊方法\n",
    "    # __new__是用来创建对象并返回这个对象\n",
    "    # 而__init__只是将传入的参数初始化给对象\n",
    "    # 实际中,你很少会用到__new__，除非你希望能够控制对象的创建\n",
    "    # 在这里，类是我们要创建的对象，我们希望能够自定义它，所以我们改写了__new__\n",
    "    # 如果你希望的话，你也可以在__init__中做些事情\n",
    "    # 还有一些高级的用法会涉及到改写__call__，但这里我们就先不这样.\n",
    "    def __new__(cls, name, bases, attrs):\n",
    "        # cls：当前创建的类对象\n",
    "        # name：类的名字\n",
    "        # bases：类继承的父类集合\n",
    "        # attrs：类的方法集合。\n",
    "        print('name: ', name) # 这些打印会在创建MyList类对象的时候打印，而非在创建MyList实例对象的时候\n",
    "        print('bases: ',bases)\n",
    "        print('attrs: ', attrs)\n",
    "        attrs['add'] = lambda self, value: self.append(value)\n",
    "        return type.__new__(cls, name, bases, attrs)\n",
    "\n",
    "class MyList(list, metaclass=ListMetaclass):\n",
    "    pass\n",
    "\n",
    "name:  MyList\n",
    "bases:  (<class 'list'>,)\n",
    "attrs:  {'__module__': '__main__', '__qualname__': 'MyList'}\n",
    ">>> l=MyList() # 在创建MyList类的时候自动获得了add属性，即一个函数对象\n",
    ">>> l.add('test1')\n",
    ">>> l.add('test2')\n",
    ">>> l\n",
    "['test1', 'test2']\n",
    "大家也可以回顾上一个小节中用元类实现单例模式。以上就是关于元类的一些介绍。\n",
    "\n",
    "问题：请描述抽象类和接口类的区别和联系\n",
    "面试官想从这道题中考察面试人什么？\n",
    "接口类和抽象类是两个比较容易混淆的概念，所以面试官希望你能了解它们的设计目的，接口是为了对类的行为的约束，而抽象类的设计目的是为了复用，实现归一化设计。\n",
    "\n",
    "答案\n",
    "接口类：定义了一些接口（就是函数，但这些函数都没有具体的实现），必须由引用类继承接口类；\n",
    "抽象类：该类不能被实例化，只能被继承，且子类必须实现抽象方法。\n",
    "一般而言，接口类和抽象类的区别和联系：\n",
    "\n",
    "接口的设计目的是 “协作” 与 “解耦合”；\n",
    "抽象类的设计目的更多的是 “复用”；\n",
    "抽象类与接口都是抽象的，抽象类和接口类一样是一种规范，规定子类应该具备的功能；\n",
    "接口类中没有实现所有的方法，抽象类实现有部分方法。\n",
    "python 中没有接口类，但支持抽象类。abc 模块就是用来实现抽象类的，介于类和接口之间的一个概念，同时具备类和接口的部分特性。\n",
    "\n",
    "拓展\n",
    "接口是什么？对于一个对象而言，就是该对象的一套公开的方法和属性集合。\n",
    "\n",
    "非正式的接口：鸭子类型 / 协议风格的接口\n",
    "\n",
    "Python 没有像 Java 那样有固定的接口语法，即 interface 关键字。在动态语言的世界中，大多数东西都是隐式的，它们更关注于如何实现对象的行为，而非对象的类型。这就是我们之前提到过的 “鸭子类型”。在 Python 里如果想实现一个可迭代对象，只需要在自定义的类中实现可迭代协议：__iter__。所以，我们并不关心你的类是什么类型，而是它可以干什么。这就是所谓的协议风格的接口：鸭子类型没有明确的接口，只是遵循了一定的协议。\n",
    "\n",
    "class Payment: # 接口类\n",
    "    def pay(self, money):\n",
    "        raise NotImplementedError\n",
    "\n",
    "class AliPay(Payment): # 引用类具体实现\n",
    "    def pay(self, money):\n",
    "        print('AliPay')\n",
    "\n",
    "class AppPay(Payment):\n",
    "    def pay(self, money):\n",
    "        print('AppPay')\n",
    "\n",
    "class WeichatPay(Payment):\n",
    "    def zhifu(self, money):\n",
    "        print('WeichatPay')\n",
    "\n",
    "def pay(payment, money):\n",
    "    # 如果实现类没有提供pay方法，在运行的过程就会报异常，\n",
    "    # 这也是我们接下来要介绍的抽象类存在的目的\n",
    "    payment.pay(money)\n",
    "\n",
    ">>> p = WeichatPay()\n",
    ">>> pay(p, 40) # 抛出NotImplementedError\n",
    "正式接口：抽象基类\n",
    "\n",
    "我们也曾提到过，大多数情况下，用鸭子类型去实现接口可以很好地运行，但也会带来一些弊端，毕竟我们在调用的时候无法知道对象的类型。而抽象类可以解决这个问题。\n",
    "\n",
    "我们去定义一个抽象基类，在这个基类中定义一些方法作为抽象方法。任何继承这个抽象基类的对象都必须实现那些方法，否则会引发 TypeError。所以，不用等到运行的时候，就会直接报错。就像上面那个例子中，如果使用抽象基类的方法，有一种新的支付方式，它没有提供 pay 方法，那么它就无法继承抽象基类。\n",
    "\n",
    "Tips: 实现抽象基类的方法就是通过使用 abc 这个内建模块。\n",
    "\n",
    "import abc\n",
    "\n",
    "class Payment(abc.ABC):    # 使用抽象基类实现接口类\n",
    "    # Payment是一个抽象基类，因为它继承了abc.ABC。\n",
    "    # 另外，你也可以让它从元类ABCMeta直接创建\n",
    "\n",
    "    # abc.abstractmethod这个装饰器去标记抽象方法\n",
    "    # 实现细节由子类去完成\n",
    "    @abc.abstractmethod\n",
    "    def pay(self):\n",
    "        pass\n",
    "\n",
    "class AliPay(Payment): # 引用类具体实现\n",
    "    def pay(self, money):\n",
    "        print('AliPay')\n",
    "\n",
    "class AppPay(Payment):\n",
    "    def pay(self, money):\n",
    "        print('AppPay')\n",
    "\n",
    "class WeichatPay(Payment):\n",
    "    def zhifu(self, money):\n",
    "        print('WeichatPay')\n",
    ">>> p = AliPay()\n",
    ">>> isinstance(p, Payment)\n",
    "True\n",
    ">>> wp = WeichatPay() # 这里就直接报错了\n",
    "Traceback (most recent call last):\n",
    "  File \"<stdin>\", line 1, in <module>\n",
    "TypeError: Cant instantiate abstract class WeichatPay with abstract methods pay\n",
    "下面我们来说说抽象基类的特点，以及它为什么适合作为接口：\n",
    "\n",
    "不能被实例化\n",
    ">>> a = Payment()\n",
    "Traceback (most recent call last):\n",
    "  File \"<stdin>\", line 1, in <module>\n",
    "TypeError: Can't instantiate abstract class Payment with abstract methods pay\n",
    "抽象基类的目的就是让别的类继承它以及实现特定的抽象方法。另外，你可以去使用抽象基类做类型检查，这样就确保了子类实现了某些特定的方法。\n",
    "\n",
    "两种不同方式的接口，鸭子类型的接口代表了动态协议特征，让 Python 实现多态，而抽象基类实现的接口更加明确，也更能验证实现是否符合接口标准，前者更为灵活，后者则会减少一些代码隐患，但也要避免过度使用抽象基类去做类型检查，否则就会让代码更加复杂，违背了 Python 的动态语言特性。\n",
    "\n",
    "在 Python 中，抽象类和接口类没有明确的界限。若是类中所有的方法都没有实现，则认为这是一个接口，若是有部分方法实现，则认为这是一个抽象类。抽象类和接口类都仅用于被继承，不能被实例化\n",
    "\n",
    "问题：新式类和旧式类的区别\n",
    "面试官想从这道题中考察面试人什么？\n",
    "新式类和旧式类的区别可以说是一个基本点的考核，尤其是新式类，它更符合面向对象编程。\n",
    "\n",
    "答案\n",
    "Python 2.x 中默认都是经典类，只有显式继承了 object 才是新式类。Python 3.x 中默认都是新式类，不必显式的继承 object；\n",
    "新式类对象可以直接通过__class__属性获取自身类型。继承搜索的顺序发生了改变，经典类多继承属性搜索顺序：先深入继承树左侧，再返回，开始找右侧；新式类多继承属性搜索顺序：先水平搜索，然后再向上移动；\n",
    "在多继承中，新式类采用广度优先搜索，而旧式类是采用深度优先搜索；\n",
    "新式类增加了__slots__内置属性，可以把实例属性的种类锁定到__slots__规定的范围之中；\n",
    "新式类增加了__getattribute__方法。\n",
    "拓展\n",
    "新式类是从 Python2.2 中引进的，它统一了类和类型的概念。简单来说，新式类就只是用户自定义的类。到了 Python3 以后就只有新式类了。\n",
    "\n",
    "为了更好地演示新式类和经典类的区别，以下代码是在 Python2.7 的版本中运行的。\n",
    "\n",
    "class Old: pass # 经典类\n",
    "class Old2: pass # 经典类\n",
    "\n",
    "class New(object): pass # 新式类\n",
    "class New2(object): pass # 新式类\n",
    "\n",
    "a = Old()\n",
    "b = Old2()\n",
    "print(type(Old))  # <type 'classobj'>\n",
    "print(type(a))    # <type 'instance'>\n",
    "print(type(a) == type(b))   # True\n",
    "\n",
    "c = New()\n",
    "d = New2()\n",
    "print(type(New))  # <type 'type'>\n",
    "print(type(c))    # <class '__main__.New>\n",
    "print(type(d))    # <class '__main__.New2>\n",
    "print(type(c) == type(d))\t# False\n",
    "小结\n",
    "这一节中基本上都是和类相关的考题，在面向对象编程中，肯定是离不开类的。\n",
    "\n",
    "元类可以动态地创建类\n",
    "接口类和抽象基类，这是之前一道面试题，关于 “鸭子类型” 和 “白鹅类型” 的另外一种考法\n",
    "经典类和新式类在不同版本的 Python 中使用有所区别，另外，新式类增加了一些新的内置属性\n",
    "希望这一小节中关于类的讲解能够帮助大家对类有了更深层次的理解。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
